Imports System.Collections.Generic
Imports System.IO
Imports System.Text.RegularExpressions
Imports System.Xml
Imports System.Xml.Schema

Public Class DocumentType

    Public Const DOCTYPE_INFO_LIST As String = "information-list"
    Public Const DOCTYPE_REQUEST As String = "request"

    Private ReadOnly _DtdMap As New Dictionary(Of String, String)

    Private Const DOCTYPE_PATTERN As String = "<!DOCTYPE(?:\s+)(?<Name>\S+)(?:(?:\s+)(?<ExternalId>.+?))?(?:\s+)?(?:\[(?<intSubset>[^]]+)\](?:\s+)?)?>"
    Private Shared ReadOnly DOCTYPE_REGEX As New Regex(DOCTYPE_PATTERN, RegexOptions.Singleline)

    Public Shared Function RemoveDocType(ByVal xmlString As String) As String

        Return DOCTYPE_REGEX.Replace(xmlString, String.Empty)

    End Function

    Public Sub New(ByVal dtdFolder As String)

        Load(DOCTYPE_INFO_LIST, Path.Combine(dtdFolder, "infolist.dtd"))
        Load(DOCTYPE_REQUEST, Path.Combine(dtdFolder, "request.dtd"))

    End Sub

    Private Sub Load(ByVal name As String, ByVal filename As String)

        Dim txt As String

        Using sr As New StreamReader(filename)
            txt = sr.ReadToEnd()
        End Using

        _DtdMap.Add(name, txt)

    End Sub

    Default Public ReadOnly Property Item(ByVal name As String)
        Get
            Return _DtdMap(name)
        End Get
    End Property

    ''' <summary>
    ''' todo: must be thread safe
    ''' </summary>
    ''' <param name="xmlWithoutDtd"></param>
    ''' <param name="name"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function Validate(ByVal xmlWithoutDtd As String, ByVal name As String) As List(Of ValidationEventArgs)

        Dim v As New Validation()

        Dim settings As New XmlReaderSettings()
        AddHandler settings.ValidationEventHandler, AddressOf v.ValidationEventHandler

        Dim ms As New MemoryStream

        Dim dtd As String = _DtdMap(name)

        Dim tr As New StringReader(xmlWithoutDtd)
        Dim xr As XmlReader = XmlReader.Create(tr, settings)
        If xr.MoveToContent() <> XmlNodeType.Element Then
            Throw New ApplicationException("document element expected")
            'Return New ValidationEventArgs
        End If
        Dim data As String = xr.ReadOuterXml()

        'prepend xml document element with dtd
        Dim xw As XmlWriter = XmlWriter.Create(ms)
        xw.WriteDocType(name, Nothing, Nothing, dtd)
        xw.WriteRaw(data)
        xw.Close()

        ms.Position = 0
        settings.ProhibitDtd = False
        settings.ValidationType = ValidationType.DTD
        Dim reader As XmlReader = XmlReader.Create(ms, settings)
        While reader.Read()
        End While

        Return v.EventList

    End Function

    Private Class Validation

        Private _Valid As Boolean = True
        Private _EventList As New List(Of ValidationEventArgs)

        Public Sub ValidationEventHandler(ByVal sender As Object, ByVal e As ValidationEventArgs)

            _Valid = False
            _EventList.Add(e)

        End Sub

        Public ReadOnly Property Valid() As Boolean
            Get
                Return _Valid
            End Get
        End Property

        Public ReadOnly Property EventList() As List(Of ValidationEventArgs)
            Get
                Return _EventList
            End Get
        End Property
    End Class
End Class
